﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Utilities;

namespace Integration
{
    public class CsvFileProcessor : TextFileProcessor
    {
        public CsvFileProcessor()
        {
            Messages = new List<FileProcessorMessage>();
            _row = new List<string>();

            OnCharacter = (reader, pos, c) =>
            {
                if (_isInQuotes) // Ignore the commas until other quotes are found
                {
                    if (c == '"')
                    {
                        if ((char)reader.Peek() == '"') // Single escaped double quotes or empty double quotes
                        {
                            HandleEscapedOrEmptyQuotes(reader, c);
                        }
                        else // Closing quote
                        {
                            _isInQuotes = false;
                        }
                    }
                    else // Append to the buffer
                    {
                        _buffer.Append(c);
                    }
                }
                else // Not in quotes
                {
                    if (c == '"')
                    {
                        if ((char)reader.Peek() == '"') // Single escaped double quotes or empty double quotes
                        {
                            HandleEscapedOrEmptyQuotes(reader, c);
                        }
                        else // Single escaped double quotes
                        {
                            _isInQuotes = true;
                        }
                    }
                    else if (c == ',') // Handle separator
                    {
                        CompleteColumn();
                    }
                    else // Append to the buffer
                    {
                        _buffer.Append(c);
                    }
                }

                return true;
            };

            OnEndOfLine = (reader, pos, eol) =>
            {
                bool keepGoing = true;

                if (_isInQuotes) // Ignore the new line
                {
                    _buffer.Append(eol);
                }
                else // Completed processing the line
                {
                    CompleteColumn();

                    try
                    {
                        keepGoing = ProcessRow();
                    }
                    catch (Exception exception)
                    {
                        Messages.Add(new FileProcessorMessage
                        {
                            RowNumber = _rowNumber,
                            Text = exception.Message
                        });

                        if (StopOnExceptions)
                        {
                            keepGoing = false;
                        }
                    }
                    finally
                    {
                        if (RowLimitReached())
                        {
                            keepGoing = false;
                        }

                        ++_rowNumber;
                        _row.Clear();
                        _colNumber = 0;
                    }
                }

                return keepGoing;
            };

            OnEndOfFile = (pos) =>
            {
                CompleteColumn();

                try
                {
                    ProcessRow();
                }
                catch (Exception exception)
                {
                    Messages.Add(new FileProcessorMessage
                    {
                        RowNumber = _rowNumber,
                        Text = exception.Message
                    });
                }
                finally
                {
                    _row.Clear();
                    _colNumber = 0;
                }
            };
        }

        private bool ProcessRow()
        {
            if (_row.Count == 0
                || (_row.Count == 1 && string.IsNullOrEmpty(_row[0])))// Empty row
            {
                if (OnEmptyRow != null)
                {
                    return OnEmptyRow(_rowNumber, Messages);
                }
                
            }
            else // Not an empty row
            {
                if (HasHeader
                && _rowNumber == 0) // Header
                {
                    if (OnHeaderFetched != null)
                    {
                        return OnHeaderFetched(_row, _rowNumber, Messages);
                    }
                }
                else
                {
                    return OnRowFetched(_row, _rowNumber, Messages);
                }
            }

            return true;
        }

        #region Properties

        public bool HasHeader { get; set; }

        public Func<IList<string>, int, IList<FileProcessorMessage>, bool> OnHeaderFetched { get; set; }

        public Func<IList<string>, int, IList<FileProcessorMessage>, bool> OnRowFetched { get; set; }

        public Func<int, IList<FileProcessorMessage>, bool> OnEmptyRow { get; set; }

        public IList<FileProcessorMessage> Messages { get; private set; }

        public bool StopOnExceptions { get; set; }

        public int? LimitRows { get; set; }

        public int ProcessedRows { get; set; }

        #endregion

        #region Fields

        private IList<string> _row;

        private bool _isInQuotes;

        private int _rowNumber;

        private int _colNumber;

        private StringBuilder _buffer = new StringBuilder();

        #endregion

        public new void Process()
        {
            _isInQuotes = false;
            _rowNumber = 0;
            _colNumber = 0;

            _row.Clear();
            _buffer.Clear();
            Messages.Clear();

            base.Process();

            ProcessedRows = _rowNumber;
        }

        #region Helpers

        private void CompleteColumn()
        {
            if (_buffer.Length > 0)
            {
                _row.Add(_buffer.ToString());
                _buffer.Clear();
            }
            else
            {
                _row.Add(string.Empty);
            }

            ++_colNumber;
        }

        private bool RowLimitReached()
        {
            if (LimitRows.HasValue && LimitRows.Value == _rowNumber)
            {
                Messages.Add(new FileProcessorMessage
                {
                    RowNumber = _rowNumber,
                    Text = string.Format("Limit of {0} rows has been reached ... stopping", _rowNumber)
                });

                return true;
            }

            return false;
        }

        private void HandleEscapedOrEmptyQuotes(StreamReader reader, char c)
        {
            char quote = (char)reader.Read();
            ++Characters;

            int nextChar = reader.Peek();

            if (nextChar == -1 // End of file
                || (    // or 
                    !_isInQuotes  // It is NOT in quotes (When in quotes column or line separators are ignored)
                    && // and
                    ((char)nextChar == ',' || (char)nextChar == '\r' || (char)nextChar == '\n'))  // It is either a column or line separator
            )
            {
                if (_buffer.Length > 0)
                {
                    Messages.Add(new FileProcessorMessage
                    {
                        RowNumber = _rowNumber,
                        Text = "Buffer is not empty: '" + _buffer.ToString() + "'"
                    });

                    _buffer.Clear();
                }
                // CompleteColumn(); It will be completed on the next iteration
                _isInQuotes = false;
            }
            else
            {
                _buffer.Append(c); // Append a single quote
            }
        }


        #endregion
    }
}
